#!/usr/bin/env python3
# -*- encoding: utf-8 -*-

'''
@file    :   pyqt_serial_main.py
@date    :   2020-06-12 22:38:37
@author  :   huenrong
'''

import sys
import os
# import traceback
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import Qt, QIODevice, QByteArray
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QApplication, QMessageBox, QInputDialog
from PyQt5.QtSerialPort import QSerialPort
from ui_pyqt_serial import Ui_py_qt_serial_demo
import serial.tools.list_ports
import datetime
import ctypes

# 记录串口波特率
g_serial_baud_rate = 0


class pyqt_serial(QtWidgets.QWidget, Ui_py_qt_serial_demo):
    """pyqt_serial pyqt serial demo界面类

    Args:
        QtWidgets (class): PyQt5.QtWidgets.QWidget类
        Ui_py_qt_serial_demo (class): ui_pyqt_serial.Ui_py_qt_serial_demo类
    """

    def __init__(self):
        """__init__ [pyqt serial demo界面类初始化]
        """
        super(pyqt_serial, self).__init__()
        # 初始化窗口
        self.setupUi(self)

        # 初始化serial
        # self.serial_param = serial.Serial()
        self.serial_param = QSerialPort()

        # 初始化信号和槽
        self.init_signal_slot()

        # 刷新串口
        self.refresh_serial()

        # 获取当前界面选择的串口波特率
        global g_serial_baud_rate
        g_serial_baud_rate = int(self.baud_rate_combo_box.currentText())

    def init_signal_slot(self):
        """init_signal_slot 初始化信号和槽
        """
        # 清空窗口按钮
        self.clear_winwods_push_button.clicked.connect(self.clear_windows)

        # 清空发送区按钮
        self.clear_send_data_push_button.clicked.connect(self.clear_send_data)

        # 刷新串口
        self.refresh_serial_push_button.clicked.connect(self.refresh_serial)

        # 打开串口
        self.open_serial_push_button.toggled.connect(self.open_serial)

        # 发送数据
        self.send_data_push_button.clicked.connect(self.send_data)

        # hex选择框
        self.hex_check_box.stateChanged.connect(
            self.hex_check_box_state_change)

        # 读取数据
        self.serial_param.readyRead.connect(self.read_data)

        # 波特率下拉框
        self.baud_rate_combo_box.activated.connect(
            self.baud_rate_combo_box_activated)

        # 串口号下拉框
        self.serial_combo_box.activated.connect(
            self.serial_combo_box_activated)

    def clear_windows(self):
        """clear_windows 清空信息显示窗口
        """
        self.show_text_edit.clear()

    def clear_send_data(self):
        """clear_send_data 清空发送区窗口
        """
        self.send_data_text_edit.clear()

    def refresh_serial(self):
        """refresh_serial 刷新串口
        """
        # 获取本机串口列表
        serial_list = list(serial.tools.list_ports.comports())
        # 清除界面串口下拉列表
        self.serial_combo_box.clear()
        # 未检测到串口, 则在下拉列表显示"无串口"
        if (0 == len(serial_list)):
            self.serial_combo_box.addItem("无串口")
        # 检测到串口, 则依次显示到下拉列表
        else:
            for display_serial in serial_list:
                # display_serial参数格式: COM4 - USB-SERIAL CH340 (COM4)
                self.serial_combo_box.addItem(display_serial[0])

    def open_serial(self, checked):
        """open_serial 打开串口

        Args:
            checked (bool): 指示按键是否被按下
        """
        # 按钮被按下, 打开串口, 并更新按钮显示为"关闭串口"
        if (True == checked):
            # self.serial_param.port = self.serial_combo_box.currentText()
            # self.serial_param.baudrate = int(
            #     self.baud_rate_combo_box.currentText())
            # self.serial_param.bytesize = 8
            # self.serial_param.stopbits = 1
            # self.serial_param.parity = serial.PARITY_NONE

            # 设置串口参数
            self.serial_param.setPortName(self.serial_combo_box.currentText())
            self.serial_param.setBaudRate(g_serial_baud_rate)
            self.serial_param.setDataBits(QSerialPort.Data8)
            self.serial_param.setStopBits(QSerialPort.OneStop)
            self.serial_param.setParity(QSerialPort.NoParity)
            self.serial_param.setFlowControl(QSerialPort.NoFlowControl)

            # 打开串口
            try:
                self.serial_param.open(QIODevice.ReadWrite)
            # 打开失败, 弹出严重错误对话框
            except Exception as identifier:
                QMessageBox.critical(self, "打开串口失败", str(identifier))
            # 打开成功, 更新按钮显示为"关闭串口"
            else:
                self.open_serial_push_button.setText("关闭串口")
        # 按钮未被按下, 关闭串口, 并更新按钮显示为"打开串口"
        else:
            # 关闭串口
            try:
                self.serial_param.close()
            # 打开失败, 弹出严重错误对话框
            except Exception as identifier:
                QMessageBox.critical(self, "关闭串口失败", str(identifier))
            # 打开成功, 更新按钮显示为"关闭串口"
            else:
                self.open_serial_push_button.setText("打开串口")

    def send_data(self):
        """send_data 发送数据
        """
        # 如果串口未打开, 弹出提示框
        if (not self.serial_param.isOpen()):
            # 显示内容后面加一点空白符, 不然标题显示不全, 暂未找到自适应的方式
            QMessageBox.information(self, "发送数据失败", "串口未打开  ")

            return

        # 如果发送数据区没有数据, 则弹出提示框
        if (not self.send_data_text_edit.toPlainText()):
            QMessageBox.information(self, "发送数据失败", "请输入发送内容")

            return

        # 读取发送数据
        data = self.send_data_text_edit.toPlainText()

        print("type(data): ", type(data))
        print("data: ", data)

        # 如果hex复选框被选中，则需要将数据转换成hex格式
        if (self.hex_check_box.checkState()):
            data_bytes = bytes.fromhex(data)

            print("type(data_bytes): ", type(data_bytes))
            print("data_bytes: ", data_bytes)

            # 发送数据
            send_data_len = self.serial_param.write(data_bytes)
        else:
            data_bytes = bytes(data, encoding="utf8")

            print("type(data_bytes): ", type(data_bytes))
            print("data_bytes: ", data_bytes)

            # 发送数据
            send_data_len = self.serial_param.write(data_bytes)

        # 发送数据成功, 在显示窗口显示发送数据
        if (send_data_len > 0):
            current_time = datetime.datetime.now().strftime('%H:%M:%S.%f')
            show_data = "[%s]->: %s" % (current_time, data)
            self.show_text_edit.append(show_data)

    def hex_check_box_state_change(self, state):
        """hex_check_box_state_change hex选择框状态改变

        Args:
            state (int): 未选中: 0; 选中: 其它
        """
        # hex选择框未被选中, 则当前为字符串模式, 设置默认显示内容
        if (0 == state):
            self.send_data_text_edit.clear()
            self.send_data_text_edit.append("hello world")
        # hex选择框被选中, 则当前为hex模式, 设置默认显示内容
        else:
            self.send_data_text_edit.clear()
            self.send_data_text_edit.append("AA BB")

    def read_data(self):
        """read_data 读取数据
        """
        # 如果有数据需要读取
        if (self.serial_param.bytesAvailable()):
            # 接收数据
            data = self.serial_param.readAll()

            # hex复选框被选中, 需要将数据转换为hex格式显示
            if (self.hex_check_box.checkState()):
                current_time = datetime.datetime.now().strftime('%H:%M:%S.%f')
                show_data = "[%s]<-: " % (current_time)

                # 循环将QByteArray转换为str
                for i in range(0, len(data)):
                    show_data = show_data + data[i].hex() + ' '

                # 显示接收数据
                self.show_text_edit.append(show_data.upper())
            # hex复选框未被选中
            else:
                current_time = datetime.datetime.now().strftime('%H:%M:%S.%f')
                show_data = "[%s]<-: %s" % (current_time,
                                            str(data, encoding="utf-8"))

                # 显示接收数据
                self.show_text_edit.append(show_data)

    def baud_rate_combo_box_activated(self, baud_rate_number):
        """baud_rate_combo_box_activated 波特率下拉框

        Args:
            baud_rate_number (int): 选择的波特率参数序号
        """
        # 若选择第8项"custom", 则需要手动输入波特率
        if (8 == baud_rate_number):
            serial_baud_rate = QInputDialog.getInt(self, "请输入波特率",
                                                   "请输入波特率, 范围[110, 2000000]",
                                                   115200, 110, 2000000, 1)
            global g_serial_baud_rate
            g_serial_baud_rate = serial_baud_rate[0]
        else:
            g_serial_baud_rate = int(self.baud_rate_combo_box.currentText())

        # 如果当前串口处于打开状态, 更改波特率后需要重新打开串口
        if (self.serial_param.isOpen()):
            # 关闭串口
            self.serial_param.close()

            # 重新打开串口
            # 设置串口参数
            self.serial_param.setPortName(self.serial_combo_box.currentText())
            self.serial_param.setBaudRate(g_serial_baud_rate)
            self.serial_param.setDataBits(QSerialPort.Data8)
            self.serial_param.setStopBits(QSerialPort.OneStop)
            self.serial_param.setParity(QSerialPort.NoParity)
            self.serial_param.setFlowControl(QSerialPort.NoFlowControl)

            # 打开串口
            try:
                self.serial_param.open(QIODevice.ReadWrite)
            # 打开失败, 弹出严重错误对话框
            except Exception as identifier:
                QMessageBox.critical(self, "打开串口失败", str(identifier))
            # 打开成功, 更新按钮显示为"关闭串口"
            else:
                self.open_serial_push_button.setText("关闭串口")

    def serial_combo_box_activated(self, serial_number):
        """serial_combo_box_activated 串口号下拉框

        Args:
            serial_number (int): 选择的串口号下拉框序号
        """
        # 如果当前串口处于打开状态, 更改波特率后需要重新打开串口
        if (self.serial_param.isOpen()):
            # 关闭串口
            self.serial_param.close()

            # 重新打开串口
            # 设置串口参数
            self.serial_param.setPortName(self.serial_combo_box.currentText())
            self.serial_param.setBaudRate(g_serial_baud_rate)
            self.serial_param.setDataBits(QSerialPort.Data8)
            self.serial_param.setStopBits(QSerialPort.OneStop)
            self.serial_param.setParity(QSerialPort.NoParity)
            self.serial_param.setFlowControl(QSerialPort.NoFlowControl)

            # 打开串口
            try:
                self.serial_param.open(QIODevice.ReadWrite)
            # 打开失败, 弹出严重错误对话框
            except Exception as identifier:
                QMessageBox.critical(self, "打开串口失败", str(identifier))
            # 打开成功, 更新按钮显示为"关闭串口"
            else:
                self.open_serial_push_button.setText("关闭串口")


if __name__ == '__main__':
    # 高清屏幕自适应
    QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)

    app = QtWidgets.QApplication(sys.argv)

    # 设置窗口图标
    icon = QIcon("./myico.ico")
    app.setWindowIcon(icon)

    # 设置任务栏图标
    ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID()

    widget = pyqt_serial()
    widget.show()
    sys.exit(app.exec_())
